package com.doodle.common.utils;

import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.security.MessageDigest;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Random;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;

public class OAuth {
	
	public static final String SIGN_METHOD_MD5 = "MD5";
	public static final String SIGN_METHOD_HMAC_SHA1 = "HMAC-SHA1";

	private final String mConsumerKey;
	private final String mConsumerSecret;
	private final String mSignMethed;
	private final int mType;

	public static final int DUOMI_OAUTH_TYPE = 0;
	public static final int MEIZU_OAUTH_TYPE = 1;
	
	private static final PostParameter sOAuthVersionParam = new PostParameter("oauth_version", "1.0");
	
	private static Random sRand = new Random();
	
	public static class OAuthToken {
	    private String mToken;
	    private String mTokenSecret;
	    
	    public OAuthToken(String token, String tokenSecret) {
	    	mToken = token;
	    	mTokenSecret = tokenSecret;
	    }

	    public String getToken() {
	        return mToken;
	    }

	    public String getTokenSecret() {
	        return mTokenSecret;
	    }
	}

	public OAuth(String consumerKey, String consumerSecret, String signMethed, int type) {
		mConsumerKey = consumerKey;
		mConsumerSecret = consumerSecret;
		mSignMethed = signMethed;
		mType = type;
	}
	
	public String generateAuthorizationHeader(String method, String url, List<PostParameter> params, OAuthToken token) {
        long timestamp = System.currentTimeMillis() / 1000;
        long nonce = timestamp + sRand.nextInt();
        return generateAuthorizationHeader(method, url, params, String.valueOf(nonce), String.valueOf(timestamp), token);
    }

	public String generateAuthorizationHeader(String method, String url,
			List<PostParameter> params, String timestamp, String nonce, OAuthToken token) {

		List<PostParameter> oauthHeaderParams = new ArrayList<PostParameter>(5);
		oauthHeaderParams.add(new PostParameter("oauth_consumer_key", mConsumerKey));
		oauthHeaderParams.add(new PostParameter("oauth_signature_method", mSignMethed));
		oauthHeaderParams.add(new PostParameter("oauth_timestamp", timestamp));
		oauthHeaderParams.add(new PostParameter("oauth_nonce", nonce));
		oauthHeaderParams.add(sOAuthVersionParam);
		
		if (null != token && !Utils.isEmpty(token.getToken())) {
            oauthHeaderParams.add(new PostParameter("oauth_token", token.getToken()));
        }

		List<PostParameter> signatureBaseParams = null;
		if (params != null) {
			signatureBaseParams = new ArrayList<PostParameter>(oauthHeaderParams.size() + params.size());
			signatureBaseParams.addAll(oauthHeaderParams);
			signatureBaseParams.addAll(params);
		} else {
			signatureBaseParams = new ArrayList<PostParameter>(oauthHeaderParams.size());
			signatureBaseParams.addAll(oauthHeaderParams);
		}
		parseGetParameters(url, signatureBaseParams);

		StringBuffer base = new StringBuffer(method).append("&").append(encode(constructRequestURL(url))).append("&");
		base.append(encode(normalizeRequestParameters(signatureBaseParams)));
		String oauthBaseString = base.toString();
		
		String signature = generateSignature(oauthBaseString, token == null ? null : token.getTokenSecret());
		if (mType == DUOMI_OAUTH_TYPE && params != null) {
			oauthHeaderParams.addAll(params);
		}
		oauthHeaderParams.add(new PostParameter("oauth_signature", signature));

		return "OAuth " + encodeParameters(oauthHeaderParams, ",", true);
	}

	private String generateSignature(String baseString, String tokenSecret) {
		byte[] byteHMAC = null;
		String data = baseString;

		if (SIGN_METHOD_HMAC_SHA1.equals(mSignMethed)) {
			if (tokenSecret != null) {
				data += encode(tokenSecret);
			}
			byteHMAC = HMCASHA1Encode(data.getBytes(), mConsumerSecret);
		} else {
			if (tokenSecret == null) {
				data += "&" + encode(mConsumerSecret) + "&";
			} else {
				data += "&" + encode(mConsumerSecret) + "&" + encode(tokenSecret);
			}
			byteHMAC = MD5Encode(data.getBytes());
		}
		return Base64Encoder.encode(byteHMAC);
	}

	private String encode(String value) {
		String encoded = null;
		try {
			if (Utils.isEmpty(value)) {
				encoded = "";
			} else {
				encoded = URLEncoder.encode(value, "UTF-8");
			}
		} catch (UnsupportedEncodingException ignore) {
		}
		StringBuffer buf = new StringBuffer(encoded.length());
		char focus;
		for (int i = 0; i < encoded.length(); i++) {
			focus = encoded.charAt(i);
			if (focus == '*') {
				buf.append("%2A");
			} else if (focus == '+') {
				buf.append("%20");
			} else if (focus == '%' && (i + 1) < encoded.length()
					&& encoded.charAt(i + 1) == '7'
					&& encoded.charAt(i + 2) == 'E') {
				buf.append('~');
				i += 2;
			} else {
				buf.append(focus);
			}
		}
		return buf.toString();
	}

	private String constructRequestURL(String url) {
		int index = url.indexOf("?");
		if (-1 != index) {
			url = url.substring(0, index);
		}
		int slashIndex = url.indexOf("/", 8);
		String baseURL = url.substring(0, slashIndex).toLowerCase();
		int colonIndex = baseURL.indexOf(":", 8);
		if (-1 != colonIndex) {
			// url contains port number
			if (baseURL.startsWith("http://") && baseURL.endsWith(":80")) {
				// http default port 80 MUST be excluded
				baseURL = baseURL.substring(0, colonIndex);
			} else if (baseURL.startsWith("https://") && baseURL.endsWith(":443")) {
				// http default port 443 MUST be excluded
				baseURL = baseURL.substring(0, colonIndex);
			}
		}
		url = baseURL + url.substring(slashIndex);

		return url;
	}

	private String normalizeRequestParameters(List<PostParameter> params) {
		Collections.sort(params);
		return encodeParameters(params, "&", false);
	}

	private String encodeParameters(List<PostParameter> postParams, String splitter, boolean quot) {
		StringBuffer buf = new StringBuffer();
		if (quot) {
			for (PostParameter param : postParams) {
				if (buf.length() != 0) {
					buf.append("\"");
					buf.append(splitter);
				}
				buf.append(encode(param.getName())).append("=");
				buf.append("\"");
				buf.append(encode(param.getValue()));
			}
			if (buf.length() != 0) {
				buf.append("\"");
			}
		} else {
			for (PostParameter param : postParams) {
				if (buf.length() != 0) {
					buf.append(splitter);
				}
				buf.append(encode(param.getName())).append("=");
				buf.append(encode(param.getValue()));
			}
		}
		return buf.toString();
	}

	private static byte[] MD5Encode(byte origin[]) {
		try {
			MessageDigest md = MessageDigest.getInstance("MD5");
			return md.digest(origin);
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}
	
	public static byte[] HMCASHA1Encode(byte origin[], String key) {
		byte[] keyBytes = key.getBytes();
		SecretKeySpec signingKey = new SecretKeySpec(keyBytes, "HmacSHA1");
		try {
			Mac mac = Mac.getInstance("HmacSHA1");
			mac.init(signingKey);
			return mac.doFinal(origin);
		} catch (Exception e) {
			e.printStackTrace();
			return new byte[0];
		}
	}

	public String getConsumerKey() {
		return mConsumerKey;
	}
	
	private void parseGetParameters(String url,
			List<PostParameter> signatureBaseParams) {
		int queryStart = url.indexOf("?");
		if (-1 != queryStart) {
			String[] queryStrs = url.substring(queryStart + 1).split("&");
			try {
				for (String query : queryStrs) {
					String[] split = query.split("=");
					if (split.length == 2) {
						signatureBaseParams.add(new PostParameter(
								URLDecoder.decode(split[0], "UTF-8"),
								URLDecoder.decode(split[1], "UTF-8")));
					} else {
						signatureBaseParams.add(new PostParameter(
								URLDecoder.decode(split[0], "UTF-8"), ""));
					}
				}
			} catch (UnsupportedEncodingException ignore) {
			}
		}
	}
}
